﻿using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using Core;
using Core.Authorization;
using Core.Domain.Entities;
using Core.Exceptions;
using Core.Identity.Common.Auth.Jwt;
using Core.Identity.Common.Interfaces.Common.Interfaces;
using Core.Identity.Tokens;
using Core.Identity.Tokens.Features.Generate;
using Core.Identity.Tokens.Features.Refresh;
using Core.Identity.Tokens.Models;
using Core.Models;
using Finbuckle.MultiTenant.Abstractions;
using Infrastructure.Tenant;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;

namespace Infrastructure.Identity.Tokens;

/// <summary>
/// 生成Token服务
/// </summary>
public class TokenService : ITokenService
{
    private readonly IIdentityDbContext _dbContext;
    private readonly IMultiTenantContextAccessor<BoilerplateTenantInfo>? _multiTenantContextAccessor;
    private readonly JwtOptions _jwtOptions;
    private readonly IPublisher _publisher;
    public TokenService(IOptions<JwtOptions> jwtOptions, IIdentityDbContext dbContext, IMultiTenantContextAccessor<BoilerplateTenantInfo>? multiTenantContextAccessor, IPublisher publisher)
    {
        _jwtOptions = jwtOptions.Value;
        _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
        _multiTenantContextAccessor = multiTenantContextAccessor;
        _publisher = publisher;
    }

    public async Task<Result<TokenResponse>> GenerateTokenAsync(TokenGenerationCommand request, string ipAddress, CancellationToken cancellationToken = default)
    {
        var currentTenant = _multiTenantContextAccessor!.MultiTenantContext.TenantInfo;
        if (currentTenant == null) throw new UnauthorizedException();
        if (string.IsNullOrWhiteSpace(currentTenant.Id)
           || await _dbContext.Users.FirstOrDefaultAsync(x => x.Username == request.Username) is not { } user
           || !await user.CheckPasswordAsync(user, request.Password))
        {
            throw new BoilerplateException("用户名或密码错误");
        }

        if (currentTenant.Id != TenantConstants.Root.Id)
        {
            if (!currentTenant.IsActive)
            {
                throw new BoilerplateException($"租户 {currentTenant.Id} 已停用");
            }


            if (DateTime.UtcNow > currentTenant.ValidUpto)
            {
                throw new BoilerplateException($"租户 {currentTenant.Id} 已过有效期 ");
            }
        }

        var permissionCodes = await GetUserPermissionCodesAsync(user);

        return await Result<TokenResponse>.SucceedAsync(await GenerateTokensAndUpdateUser(user, ipAddress, permissionCodes));
    }


    public async Task<Result<TokenResponse>> RefreshTokenAsync(RefreshTokenCommand request, string ipAddress, CancellationToken cancellationToken = default)
    {
        var userPrincipal = GetPrincipalFromExpiredToken(request.Token);
        var userId = Guid.Parse(userPrincipal.FindFirstValue(ClaimTypes.NameIdentifier)!);
        var user = await _dbContext.Users.FindAsync(userId);
        if (user is null)
        {
            throw new UnauthorizedException();
        }

        if (request.RefreshTokenExpiryTime <= DateTime.UtcNow)
        {
            throw new UnauthorizedException("无效的刷新令牌");
        }
        var permissionCodes = await GetUserPermissionCodesAsync(user);
        return await Result<TokenResponse>.SucceedAsync(await GenerateTokensAndUpdateUser(user, ipAddress, permissionCodes));
    }
    private async Task<TokenResponse> GenerateTokensAndUpdateUser(User user, string ipAddress, List<string> permissionCodes)
    {
        string token = GenerateJwt(user, ipAddress, permissionCodes);

        var refreshToken = GenerateRefreshToken();
        var refreshTokenExpiryTime = DateTime.UtcNow.AddDays(_jwtOptions.RefreshTokenExpirationInDays);

        return await Task.FromResult(new TokenResponse(token, refreshToken, refreshTokenExpiryTime));
    }

    private string GenerateJwt(User user, string ipAddress, List<string> permissionCodes) =>
    GenerateEncryptedToken(GetSigningCredentials(), GetClaims(user, ipAddress, permissionCodes));

    private SigningCredentials GetSigningCredentials()
    {
        byte[] secret = Encoding.UTF8.GetBytes(_jwtOptions.Key);
        return new SigningCredentials(new SymmetricSecurityKey(secret), SecurityAlgorithms.HmacSha256);
    }

    private string GenerateEncryptedToken(SigningCredentials signingCredentials, IEnumerable<Claim> claims)
    {
        var token = new JwtSecurityToken(
           claims: claims,
           expires: DateTime.UtcNow.AddMinutes(_jwtOptions.TokenExpirationInMinutes),
           signingCredentials: signingCredentials,
           issuer: JwtAuthConstants.Issuer,
           audience: JwtAuthConstants.Audience
           );
        var tokenHandler = new JwtSecurityTokenHandler();
        return tokenHandler.WriteToken(token);
    }

    private List<Claim> GetClaims(User user, string ipAddress, List<string> permissionCodes) =>
        new List<Claim>
        {
            new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
            new(ClaimTypes.NameIdentifier, user.Id.ToString()),
            new(ClaimTypes.Email, user.Email ?? string.Empty),
            new(ClaimTypes.Name, user.Username ?? string.Empty),
            new(ClaimTypes.MobilePhone, user.PhoneNumber ?? string.Empty),
            new(ClaimTypes.Surname, user.Nickname ?? string.Empty),
            new(BoilerplateClaims.IpAddress, ipAddress),
            new(BoilerplateClaims.Tenant, _multiTenantContextAccessor!.MultiTenantContext.TenantInfo!.Id),
            new(BoilerplateClaims.ImageUrl, user.Avatar == null ? string.Empty : user.Avatar.ToString()),
            new(BoilerplateClaims.Permission,JsonSerializer.Serialize(permissionCodes)),
            new(BoilerplateClaims.IsSuperAdmin,user.IsSuperAdmin == null ? string.Empty:user.IsSuperAdmin.Value.ToString())
        };
    private static string GenerateRefreshToken()
    {
        byte[] randomNumber = new byte[32];
        using var rng = RandomNumberGenerator.Create();
        rng.GetBytes(randomNumber);
        return Convert.ToBase64String(randomNumber);
    }

    private ClaimsPrincipal GetPrincipalFromExpiredToken(string token)
    {
        var tokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtOptions.Key)),
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidAudience = JwtAuthConstants.Audience,
            ValidIssuer = JwtAuthConstants.Issuer,
            RoleClaimType = ClaimTypes.Role,
            ClockSkew = TimeSpan.Zero,
            ValidateLifetime = false
        };

        var tokenHandler = new JwtSecurityTokenHandler();
        var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out var securityToken);
        if (securityToken is not JwtSecurityToken jwtSecurityToken ||
            !jwtSecurityToken.Header.Alg.Equals(
                SecurityAlgorithms.HmacSha256,
                StringComparison.OrdinalIgnoreCase))
        {
            throw new UnauthorizedException("无效的令牌");
        }

        return principal;
    }

    /// <summary>
    /// 获取指定用户的权限代码列表
    /// </summary>
    /// <param name="user">用户</param>
    /// <param name="menuType">菜单类型（默认3=操作权限）</param>
    /// <param name="cancellationToken">取消标记</param>
    public async Task<List<string>> GetUserPermissionCodesAsync(
        User user,
        int menuType = 3,
        CancellationToken cancellationToken = default)
    {
        if (user.IsSuperAdmin.HasValue && user.IsSuperAdmin.Value == true)
        {
            return await _dbContext.Permissions
                .Where(p => p != null && p.MenuType == menuType)
                .Select(p => p!.Code)
                .Distinct()
                .ToListAsync(cancellationToken)
                .ConfigureAwait(false);
        }

        return await _dbContext.UserRoles
            .Where(ur => ur.UserId == user.Id)
            .Select(ur => ur.Role)
            .Where(role => role != null)
            .SelectMany(role => role!.Permissions)
            .Select(rp => rp.Permission)
            .Where(p => p != null && p.MenuType == menuType)
            .Select(p => p!.Code)
            .Distinct()
            .ToListAsync(cancellationToken)
            .ConfigureAwait(false);
    }
}
